#pragma once

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include "SpinLock.h"
#include "BufList.h"
#include "BufBlock.h"

template <typename CLock>
class CBufMgr;

template <typename CLock = CFakeLock>
class CBuffer
{
    typedef CBufList<BufBlock, CLock> CBlockList;
    typedef typename CBlockList::ListNode BlockNode;

public:
    CBuffer()
    {
        m_iDataLen = 0;
        m_pBufMgr = nullptr;
    }

    virtual ~CBuffer()
    {
        Destroy();
    }

    void Destroy()
    {
        BlockNode *pHead = nullptr;

        if (nullptr == m_pBufMgr)
            return;

        while (0 < m_lstData.GetSize())
        {
            pHead = (BlockNode *)m_lstData.GetHeader();

            if (nullptr == pHead)
                return;

            m_lstData.Remove(pHead);
            m_pBufMgr->FreeBlock(pHead);
        }
    }

public:
    int Read(void *pDest, int iLen)
    {
        IoResult res;
        int iRead = 0;
        BlockNode *pHead = (BlockNode *)m_lstData.GetHeader();

        if ((nullptr == pDest) || (iLen <= 0))
            return -1;

        if (nullptr == pHead)
            return -1;

        while (iLen > 0)
        {
            res = pHead->Read((uint8_t *)pDest + iRead, iLen);
            iRead += res.nLen;
            iLen -= res.nLen;
            m_iDataLen -= res.nLen;

            if (!res.bRes)
                break;

            if (res.bEnd)
            {
                BlockNode *pNode = (BlockNode *)pHead->Next();

                if (nullptr == pNode || nullptr == m_pBufMgr)
                    return -1;

                m_lstData.Remove(pHead);
                m_pBufMgr->FreeBlock(pHead);
                pHead = pNode;
            }
        }

        return iRead;
    }

    bool ReadLine(void *pDest, uint32_t &nRead)
    {
        IoResult res;
        BlockNode *pHead = (BlockNode *)m_lstData.GetHeader();
        nRead = 0;

        if ((nullptr == pDest) || (nullptr == pHead))
            return -1;

        while (true)
        {
            res = pHead->ReadLine((uint8_t *)pDest + nRead);
            nRead += res.nLen;
            m_iDataLen -= res.nLen;

            if (res.bRes)
                break;

            if (res.bErr)
                break;

            if (res.bEnd)
            {
                BlockNode *pNode = (BlockNode *)pHead->Next();

                if (nullptr == pNode || nullptr == m_pBufMgr)
                    return -1;

                m_lstData.Remove(pHead);
                m_pBufMgr->FreeBlock(pHead);
                pHead = pNode;
            }
        }

        return res.bRes;
    }

    int Write(void *pSrc, int iLen)
    {
        IoResult res;
        int iWrote = 0;
        BlockNode *pTail = (BlockNode *)m_lstData.GetTailer();

        if ((nullptr == pSrc) || (iLen <= 0))
            return -1;

        if ((nullptr == pTail) || ((nullptr != pTail) && (pTail->nWrite == pTail->nEnd)))
        {
            if (nullptr == m_pBufMgr)
                return -1;

            pTail = m_pBufMgr->GetBlock();

            if (nullptr == pTail)
                return -1;

            m_lstData.PushBack(pTail);
        }

        while (iLen > 0)
        {
            res = pTail->Write((uint8_t *)pSrc + iWrote, iLen);
            iWrote += res.nLen;
            iLen -= res.nLen;
            m_iDataLen += res.nLen;

            if (!res.bRes)
                break;

            if (res.bEnd)
            {
                if (nullptr == m_pBufMgr)
                    return -1;

                pTail = m_pBufMgr->GetBlock();

                if (nullptr == pTail)
                    return -1;

                m_lstData.PushBack(pTail);
            }
        }

        return iWrote;
    }

    // equivalent to Write
    int Recv(int ifd, SSL *pssl)
    {
        IoResult res;
        int iRecv = 0;
        BlockNode *pTail = (BlockNode *)m_lstData.GetTailer();

        if ((nullptr == pTail) || ((nullptr != pTail) && (pTail->nWrite == pTail->nEnd)))
        {
            if (nullptr == m_pBufMgr)
                return -1;

            pTail = m_pBufMgr->GetBlock();

            if (nullptr == pTail)
                return -1;

            m_lstData.PushBack(pTail);
        }

        while (true)
        {
            res = pTail->Recv(ifd, pssl);

            if (res.bErr)
                return -1;

            if (res.bEnd)
            {
                if (nullptr == m_pBufMgr)
                    return -1;

                iRecv += res.nLen;
                m_iDataLen += res.nLen;
                pTail = m_pBufMgr->GetBlock();

                if (nullptr == pTail)
                    return -1;

                m_lstData.PushBack(pTail);
            }

            if (res.bRes)
            {
                iRecv += res.nLen;
                m_iDataLen += res.nLen;
                break;
            }
        }

        return iRecv;
    }

    // equivalent to Read
    int Send(int ifd, SSL *pssl)
    {
        IoResult res;
        int iSent = 0;
        BlockNode *pHead = (BlockNode *)m_lstData.GetHeader();

        if (nullptr == pHead)
            return -1;

        m_lock.lock();

        while (m_iDataLen > 0)
        {
            res = pHead->Send(ifd, pssl);

            if (res.bErr)
            {
                m_lock.unlock();
                return -1;
            }

            if (res.bEnd)
            {
                iSent += res.nLen;
                m_iDataLen -= res.nLen;

                BlockNode *pNode = (BlockNode *)pHead->Next();

                if (nullptr == pNode)
                {
                    m_lock.unlock();
                    return -1;
                }

                if (nullptr == m_pBufMgr)
                {
                    m_lock.unlock();
                    return -1;
                }

                m_lstData.Remove(pHead);
                m_pBufMgr->FreeBlock(pHead);
                pHead = pNode;
            }

            if (res.bRes)
            {
                iSent += res.nLen;
                m_iDataLen -= res.nLen;

                break;
            }
        }

        m_lock.unlock();
        return iSent;
    }

    int Send(int ifd, SSL *pssl, void *pSrc, int iLen)
    {
        int iRet = 0;
        int iSent = 0;

        if ((nullptr == pSrc) || (iLen <= 0))
            return -1;

        m_lock.lock();

        // 在没有数据时才可以直接发送
        if (0 >= m_iDataLen)
        {
            iRet = SendData(ifd, pssl, pSrc, iLen);

            if (iRet >= iLen)
            {
                m_lock.unlock();
                return iRet;
            }

            iLen -= iRet;
            iSent += iRet;
            pSrc = (uint8_t *)pSrc + iRet;
        }

        iRet = Write(pSrc, iLen);
        m_lock.unlock();

        if (-1 == iRet)
            return iRet;

        iSent += iRet;
        return iSent;
    }

    // void SetSocket(int ifd) { m_ifd = ifd; }
    int GetDataLength() { return m_iDataLen; }
    void SetBufMgr(CBufMgr<CLock> *pBufMgr) { m_pBufMgr = pBufMgr; }
    CBufMgr<CLock> *GetBufMgr() { return m_pBufMgr; }

private:
    int SendData(int ifd, SSL *pssl, void *pSrc, int iLen)
    {
        int iRet = 0;
        int iSent = 0;

        while (0 < iLen)
        {
            if (nullptr == pssl)
                iRet = write(ifd, pSrc, iLen);
            else
                iRet = SSL_write(pssl, pSrc, iLen);

            if (0 < iRet)
            {
                iSent += iRet;

                if (iRet >= iLen)
                    break;

                pSrc = (uint8_t *)pSrc + iRet;
                iLen -= iRet;
            }
            else
            {
                if (-1 == iRet)
                {
                    if (EAGAIN == errno)
                        break;
                    else if (EINTR == errno)
                        continue;
                }

                iSent = -1;
                break;
            }
        }

        return iSent;
    }

private:
    // int m_ifd;
    int m_iDataLen;
    CBlockList m_lstData;
    CBufMgr<CLock> *m_pBufMgr;
    CLock m_lock;
};

// 接收buffer不发送，发送buffer不接收,确保多线程情况下不同时发送或同时接收
// Send buffer 加锁

// enum class ELockType
// {
//     None,
//     Recv,
//     Send
//     // Both
// };

// template <typename CLock = CFakeLock, ELockType LockType = ELockType::None>